#include "EncodingHelper.h"
#include <iconv.h>

LIB_UTIL_NAMESPACE_BEGIN

char* szEncodingText[] = {
	"utf-8",
	"gbk",
	"gb2312",
	"unicode",
};

unsigned char toHex(unsigned char x) 
{
	return x > 9 ? x + 55: x + 48;
}

EncodingHelper::EncodingHelper(void)
{
}

EncodingHelper::~EncodingHelper(void)
{
}

bool EncodingHelper::TransformCharset(const char* szIn, 
									  char** szOut, 
									  const char* szCharsetIn,
									  const char* szCharsetOut)
{
	if(szIn == NULL || szCharsetIn == NULL || szCharsetOut == NULL)
		return false;

	iconv_t conv;
	const char** inChar = &szIn;
	size_t inSize = strlen(szIn);
	size_t outSize = inSize * 2 + 1;
	size_t size = outSize;

	conv = iconv_open(szCharsetOut, szCharsetIn);
	if(conv == (iconv_t) - 1) 
		return false;

	char* outChar = (char*)malloc(outSize);
	memset(outChar, 0, outSize);
	*szOut = outChar;
#ifndef _LINUX_
	iconv(conv, inChar, &inSize, &outChar, &outSize); 
#else
	iconv(conv, (char**)inChar, &inSize, &outChar, &outSize);
#endif
	iconv_close(conv);
	return true;
}

bool EncodingHelper::TransformCharset(const wchar_t* szIn, 
									  char** szOut, 
									  const char* szCharsetIn,
									  const char* szCharsetOut)
{
	char* szTmp = NULL;
	if(!UnicodeToUTF8(szIn, &szTmp))
	{
		return false;
	}
	if(strcmp(szCharsetOut, "utf-8") == 0)
	{
		*szOut = szTmp;
		return true;
	}
	
	return TransformCharset(szTmp, szOut, "utf-8", szCharsetOut);
}

bool EncodingHelper::TransformCharset(const char* szIn, 
									  wchar_t** szOut, 
									  const char* szCharsetIn,
									  const char* szCharsetOut)
{
	char* szTmp = NULL;
	if(strcmp(szCharsetIn, "utf-8") != 0)
		TransformCharset(szIn, &szTmp, szCharsetIn, "utf-8");
	else
		szTmp = (char*)szIn;

	return UTF8ToUnicode(szTmp, szOut);
}

bool EncodingHelper::TransformCharset(const char* szIn, 
									  char** szOut, 
									  EncodingType inType, 
									  EncodingType outType)
{
	return TransformCharset(szIn, szOut, szEncodingText[inType], szEncodingText[outType]);
}

bool EncodingHelper::UrlEncode(const char* szIn, 
							   char** szOut,
							   const char* szCharsetIn,
							   const char* szCharsetOut)
{
	char* szTransform = (char*)szIn;
	if(szCharsetIn != NULL && szCharsetOut != NULL)
		TransformCharset(szIn, &szTransform, szCharsetIn, szCharsetOut);
	
	unsigned char* inChar = (unsigned char*)szTransform;

	int inLen = strlen((char*)inChar) * 3 + 1;
	unsigned char* outChar = (unsigned char*)malloc(inLen);
	memset(outChar, 0, inLen);
	*szOut = (char*)outChar;

	while(*inChar)
	{
		if(isalnum(*inChar))
		{
			*outChar++ = *inChar++;
			continue;
		}

		if(isspace(*inChar))
		{
			*outChar++ = '+';
		}
		else
		{
			*outChar++ = '%';
			*outChar++ = toHex(*inChar >> 4);
			*outChar++ = toHex(*inChar % 16);
		}
		inChar++;
	}
	*outChar = 0;
	if(szTransform != szIn)
		FreeString((char*)szTransform);
	return true;
}

bool EncodingHelper::UnicodeToUTF8(const wchar_t* szIn, char** szOut)
{
	if(szIn == NULL || szOut == NULL)
		return false;

	int len = wcslen(szIn);
	*szOut = new char[len * 6 + 1];
	char* tmp = *szOut;
	int size = 0;
	for(int i = 0; i < len; i++)
	{
		wchar_t ch = szIn[i];
		if(ch < 0x80) ///0xxxxxxx
		{
			tmp[size++] = ch;
		}
		else if(ch < 0x800) ///110xxxxx 10xxxxxx
		{
			tmp[size++] = (0xC0 | ch >> 6);
			tmp[size++] = (0x80 | ch & 0x3F);
		}
		else if(ch < 0x10000) ///1110xxxx 10xxxxxx 10xxxxxx
		{
			tmp[size++] = (0xE0 | ch >> 12);
			tmp[size++] = (0x80 | ch >> 6 & 0x3F);
			tmp[size++] = (0x80 | ch & 0x3F);
		}
		else if(ch < 0x20000) ///11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
		{
			tmp[size++] = (0xF0 | ch >> 18);
			tmp[size++] = (0x80 | ch >> 12 & 0x3F);
			tmp[size++] = (0x80 | ch >> 6 & 0x3F);
			tmp[size++] = (0x80 | ch & 0x3F);
		}
		else if(ch < 0x40000) ///111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
		{
			tmp[size++] = (0xF8 | ch >> 24);
			tmp[size++] = (0x80 | ch >> 18 & 0x3F);
			tmp[size++] = (0x80 | ch >> 12 & 0x3F);
			tmp[size++] = (0x80 | ch >> 6 & 0x3F);
			tmp[size++] = (0x80 | ch & 0x3F);
		}
		else if(ch < 0x80000) ///1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
		{
			tmp[size++] = (0xFC | ch >> 30);
			tmp[size++] = (0x80 | ch >> 24 & 0x3F);
			tmp[size++] = (0x80 | ch >> 18 & 0x3F);
			tmp[size++] = (0x80 | ch >> 12 & 0x3F);
			tmp[size++] = (0x80 | ch >> 6 & 0x3F);
			tmp[size++] = (0x80 | ch & 0x3F);
		}
	}
	tmp[size++] = 0;
	tmp = *szOut;
	*szOut = (char*)malloc(sizeof(char) * size);
	memcpy(*szOut, tmp, sizeof(char) * size);
	delete[] tmp;
	return true;
}

bool EncodingHelper::UTF8ToUnicode(const char* szIn, wchar_t** szOut)
{
	if(szIn == NULL || szOut == NULL)
		return false;

	int len = strlen(szIn);
	*szOut = new wchar_t[len + 1];
	wchar_t* tmp = *szOut;
	int size = 0;
	for(int i = 0; i < len; i++)
	{
		unsigned char ch = szIn[i];
		if((ch & 0x80) == 0) ///0xxxxxxx
		{
			tmp[size++] = ch;
		}
		else if((ch & 0xE0) == 0xC0)  /// 110x-xxxx 10xx-xxxx
		{
			tmp[size] = (szIn[i++] & 0x3F) << 6;
			tmp[size++] |= (szIn[i] & 0x3F);
		}
		else if((ch & 0xF0) == 0xE0)  /// 1110-xxxx 10xx-xxxx 10xx-xxxx
		{
			tmp[size]  = (szIn[i++] & 0x1F) << 12;
			tmp[size] |= (szIn[i++] & 0x3F) << 6;
			tmp[size++] |= (szIn[i] & 0x3F);
		} 
		else if((ch & 0xF8) == 0xF0)  /// 1111-0xxx 10xx-xxxx 10xx-xxxx 10xx-xxxx
		{
			tmp[size]  = (szIn[i++] & 0x0F) << 18;
			tmp[size]  = (szIn[i++] & 0x3F) << 12;
			tmp[size] |= (szIn[i++] & 0x3F) << 6;
			tmp[size++] |= (szIn[i] & 0x3F);
		} 
		else if((ch & 0xFC) == 0xF8) /// 1111-10xx 10xx-xxxx 10xx-xxxx 10xx-xxxx 10xx-xxxx
		{
			tmp[size]  = (szIn[i++] & 0x07) << 24;
			tmp[size]  = (szIn[i++] & 0x3F) << 18;
			tmp[size] = (szIn[i++] & 0x3F) << 12;
			tmp[size] |= (szIn[i++] & 0x3F) << 6;
			tmp[size++] |= (szIn[i] & 0x3F);
		}
		else if((ch & 0xFE) == 0xFC) ///1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
		{
			tmp[size]  = (szIn[i++] & 0x03) << 30;
			tmp[size]  = (szIn[i++] & 0x3F) << 24;
			tmp[size]  = (szIn[i++] & 0x3F) << 18;
			tmp[size] = (szIn[i++] & 0x3F) << 12;
			tmp[size] |= (szIn[i++] & 0x3F) << 6;
			tmp[size++] |= (szIn[i] & 0x3F);
		}
	}
	tmp[size++] = 0;
	*szOut = (wchar_t*)malloc(sizeof(wchar_t) * size);
	memcpy(*szOut, tmp, sizeof(wchar_t) * size);
	delete[] tmp;
	return true;
}

bool EncodingHelper::UrlEncode(const char* szIn, 
							   char** szOut, 
							   EncodingType inType, 
							   EncodingType outType)
{
	return UrlEncode(szIn, szOut, szEncodingText[inType], szEncodingText[outType]);
}

void EncodingHelper::FreeString(void* sz)
{
	free(sz);
}

LIB_UTIL_NAMESPACE_END
